﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace Orz.Framework.Core.Messaging
{
    /// <summary>
    /// Provides loosely-coupled messaging between
    /// various colleague objects.  All references to objects
    /// are stored weakly, to prevent memory leaks.
    /// </summary>
    /// <typeparam name="T">The message type.</typeparam>
    public abstract class Messenger<T>
    {
        #region Fields

        /// <summary>
        /// The map of message to action.
        /// </summary>
        protected readonly MessageToActionsMap<T> _messageToActionsMap = new MessageToActionsMap<T>();

        #endregion Fields

        #region Subscribe

        /// <summary>
        /// Subscribe a callback method, with no parameter, to be invoked when a specific message is broadcasted.
        /// </summary>
        /// <param name="msg">The message to register for.</param>
        /// <param name="callback">The callback to be called when this message is broadcasted.</param>
        public void Subscribe(T msg, Delegate callback)
        {
            if (callback != null)
                _messageToActionsMap.AddAction(msg, callback);
        }

        /// <summary>
        /// Unsubscribes the specified delegate.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        /// <param name="callback">The callback.</param>
        public void Unsubscribe(T msg, Delegate callback)
        {
            _messageToActionsMap.RemoveAction(msg, callback);
        }

        /// <summary>
        /// Unsubscribes the specified MSG.
        /// </summary>
        /// <param name="msg">The MSG.</param>
        public void Unsubscribe(T msg)
        {
            _messageToActionsMap.RemoveActions(msg);
        }

        #endregion Register

        #region Notification

        /// <summary>
        /// Notifies all registered parties that a message is being broadcasted.
        /// </summary>
        /// <param name="msg">The message to broadcast.</param>
        /// <param name="args">The args.</param>
        public virtual void NotifyAll(T msg, params object[] args)
        {
            if (msg == null)
                return;

            var actions = _messageToActionsMap[msg];
            DoCallback(actions, false, args);
        }

        /// <summary>
        /// Notifies all registered parties that a message is being broadcasted, the process is asynchronous.
        /// </summary>
        /// <param name="msg">The message to broadcast.</param>
        /// <param name="args">The args.</param>
        public virtual void NotifyAllAsyn(T msg, params object[] args)
        {
            if (msg == null)
                return;

            var actions = _messageToActionsMap[msg];
            DoCallback(actions, true, args);
        }

        /// <summary>
        /// Determines whether [is match param] [the specified callback].
        /// </summary>
        /// <param name="callback">The callback.</param>
        /// <param name="args">The args.</param>
        /// <returns>
        /// 	<c>true</c> if [is match param] [the specified callback]; otherwise, <c>false</c>.
        /// </returns>
        protected virtual bool IsMatchParam(Delegate callback, params object[] args)
        {
            if (callback == null)
                return false;

            ParameterInfo[] paramTypes = callback.Method.GetParameters();
            if (args == null)
                return paramTypes == null || paramTypes.Length == 0;

            if (paramTypes.Length == args.Length)
            {
                return true;

                #region Strict match
                //for (int i = 0; i < paramTypes.Length; ++i)
                //{
                //    ParameterInfo pi = paramTypes[i];
                //    object arg = args[i];
                //    if (arg == null && !pi.ParameterType.IsValueType)
                //        continue;
                //    if (pi.ParameterType.IsAssignableFrom(arg.GetType()))
                //        continue;
                //    return false;
                //}
                #endregion
            }
            return false;
        }

        /// <summary>
        /// Does the callback.
        /// </summary>
        /// <param name="actions">The actions.</param>
        /// <param name="isAsyn">if set to <c>true</c> [is asyn].</param>
        /// <param name="args">The args.</param>
        protected virtual void DoCallback(List<WeakAction> actions, bool isAsyn, params object[] args)
        {
            if (actions != null)
            {
                VoidDelegateHandler handler = new VoidDelegateHandler(() =>
                {
                    actions.ForEach(action =>
                    {
                        Delegate callback = action.CreateAction();
                        if (IsMatchParam(callback, args))
                            callback.DynamicInvoke(args);
                    });
                });

                if (isAsyn)
                {
                    handler.BeginInvoke(null, null);
                }
                else
                {
                    handler.Invoke();
                }
            }
        }

        #endregion Notification

        #region MessageToActionsMap [nested class]

        /// <summary>
        /// This class is an implementation detail of the Messenger class.
        /// </summary>
        protected class MessageToActionsMap<T>
        {
            readonly Dictionary<T, List<WeakAction>> _map = new Dictionary<T, List<WeakAction>>();

            #region Methods

            /// <summary>
            /// Adds the action.
            /// </summary>
            /// <param name="msg">The MSG.</param>
            /// <param name="action">The action.</param>
            internal void AddAction(T msg, Delegate action)
            {
                if (msg == null)
                    throw new ArgumentNullException("msg");

                if (action == null)
                    throw new ArgumentNullException("action");

                lock (_map)
                {
                    if (!_map.ContainsKey(msg))
                        _map[msg] = new List<WeakAction>();

                    _map[msg].Add(new WeakAction(action));
                }
            }

            /// <summary>
            /// Removes the action.
            /// </summary>
            /// <param name="msg">The MSG.</param>
            /// <param name="action">The action.</param>
            internal void RemoveAction(T msg, Delegate action)
            {
                if (msg == null)
                    throw new ArgumentNullException("msg");

                if (action == null)
                    throw new ArgumentNullException("action");

                lock (_map)
                {
                    List<WeakAction> actionList = _map[msg];
                    if (actionList != null)
                    {
                        Type delegateType = action.GetType();
                        MethodInfo method = action.Method;
                        object target = action.Target;
                        int index = -1;
                        //Find the index of specified action
                        for (int i = 0; i < actionList.Count; ++i)
                        {
                            WeakAction item = actionList[i];
                            if (item._delegateType == delegateType
                                && item._method == method
                                && (item._targetRef == null ? target == null : item._targetRef.Target == target))
                            {
                                index = i;
                                break;
                            }
                        }

                        //Remove the item
                        if (index > -1)
                        {
                            actionList.RemoveAt(index);
                        }
                    }
                }
            }

            /// <summary>
            /// Removes the actions by key.
            /// </summary>
            /// <param name="msg">The MSG.</param>
            internal void RemoveActions(T msg)
            {
                lock (_map)
                {
                    if (_map.ContainsKey(msg))
                    {
                        _map.Remove(msg);
                    }
                }
            }

            /// <summary>
            /// Gets the <see cref="System.Collections.Generic.List&lt;Orz.FrameworkSDK.Messenger.WeakAction&gt;"/> with the specified MSG type.
            /// </summary>
            /// <value></value>
            internal List<WeakAction> this[T msg]
            {
                get
                {
                    if (msg == null)
                        return null;

                    if (!_map.ContainsKey(msg))
                        return null;

                    List<WeakAction> actions = _map[msg];

                    if (actions == null || actions.Count == 0)
                    {
                        _map.Remove(msg);
                        return null;
                    }

                    return actions;
                }
            }

            #endregion
        }

        #endregion MessageToActionsMap [nested class]

        #region WeakAction [nested class]

        /// <summary>
        /// This class is an implementation detail of the MessageToActionsMap class.
        /// </summary>
        protected class WeakAction
        {
            #region Fields

            internal Type _delegateType;
            internal readonly MethodInfo _method;
            internal readonly WeakReference _targetRef;
            internal readonly object[] _parameters;

            #endregion Fields

            #region Constructor

            internal WeakAction(Delegate action)
            {
                if (action != null)
                {
                    _delegateType = action.GetType();
                    object target = action.Target;
                    MethodInfo method = action.Method;

                    if (target == null)
                        _targetRef = null;
                    else
                        _targetRef = new WeakReference(target);

                    _method = method;
                }
            }

            #endregion Constructor

            #region Method

            /// <summary>
            /// Creates the action.
            /// </summary>
            /// <returns></returns>
            internal Delegate CreateAction()
            {
                if (_targetRef == null)
                {
                    return Delegate.CreateDelegate(_delegateType, _method);
                }
                else
                {
                    try
                    {
                        object target = _targetRef.Target;
                        if (target != null)
                            return Delegate.CreateDelegate(_delegateType, target, _method);
                    }
                    catch { }
                }

                return null;
            }

            #endregion Method
        }

        #endregion WeakAction [nested class]
    }
}